////////////////////////////////////////
//	File Header
//	Author: Stephen Ringer | stephen.ringer@gmail.com
//	File: SAGASocketObject.cpp
//
////////////////////////////////////////

////////////////////////////////////////
//	File Includes
#include <windows.h>       // type definitions
#include "SAGAWS.h"         // class definition
//
////////////////////////////////////////

////////////////////////////////////////
//	External Variables
extern bool g_bLF;
extern int g_PkgSize;
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: SAGASocketObject
//	Purpose:
//		Initialize this socket object and let it know the logfile.
SAGASocketObject::SAGASocketObject(FILE *pLog) 
{
	m_skSocket   = INVALID_SOCKET;
	m_pLog       = pLog;
	m_bRunning   = false;
	m_pInbox     = NULL;
	m_Buffer     = NULL;
}
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: ~SAGASocketObject
//	Purpose:
//		Destructor: Just disconnect this socket object.
SAGASocketObject::~SAGASocketObject() 
{ 
	if (IsRunning()) 
	{
		Disconnect();
		m_bRunning = false;
	}
	if (m_pInbox) 
	{
		delete m_pInbox;
		m_pInbox = NULL;
	}
	if (m_Buffer) 
	{
		delete [] m_Buffer;
		m_Buffer = NULL;
	}
	m_skSocket = INVALID_SOCKET;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: CreateSocket
//	Purpose:
//		Now we go and create a socket, this is needed for clients and 
//		servers as first step.
HRESULT SAGASocketObject::CreateSocket() 
{
	// if socket is in use then close it
	if (m_skSocket != INVALID_SOCKET) Disconnect();

	m_skSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (m_skSocket==INVALID_SOCKET) 
	{
		Log("error: socket() failed");
		return SAGA_FAIL;
	}
	m_pInbox = new SAGAQueue();
	m_Buffer = new char[65536];
	memset(m_Buffer,0,65536);
	return SAGA_OK;
}
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Bind
//	Purpose:
//		Bind the socket of this object to a given port.
HRESULT SAGASocketObject::Bind(int nPort) 
{
	sockaddr_in saServerAddress;

	memset(&saServerAddress, 0, sizeof(sockaddr_in));
	saServerAddress.sin_family = AF_INET;
	saServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
	saServerAddress.sin_port = htons(nPort);

	if ( bind(m_skSocket, (sockaddr*)&saServerAddress, sizeof(sockaddr)) == SOCKET_ERROR) 
	{
		LogLastWSAError("bind() failed");
		Disconnect();
		return SAGA_FAIL;
	}

	return SAGA_OK;
}
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Listen
//	Purpose:
//		Listen on the connected socket.
HRESULT SAGASocketObject::Listen() 
{
	if (listen(m_skSocket, 32) != 0) 
	{
		Log("error: listen() failed");
		return SAGA_FAIL;
	}
	m_bRunning = true;
	return SAGA_OK;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Accept
//	Purpose:
//		Accept the connection from a connection waiting in the queue.
HRESULT SAGASocketObject::Accept(SOCKET *skToNewClient) 
{
	sockaddr_in saClientAddress;
	int         nClientSize = sizeof(sockaddr_in);

	(*skToNewClient) = accept(m_skSocket, (sockaddr*)&saClientAddress, &nClientSize);
	if ((*skToNewClient) == INVALID_SOCKET) 
	{
		Log("error: accept() failed");
		return SAGA_FAIL;
	}
	Log("client from %d.%d.%d.%d now successfully connected to us",
		saClientAddress.sin_addr.S_un.S_un_b.s_b1,
		saClientAddress.sin_addr.S_un.S_un_b.s_b2,
		saClientAddress.sin_addr.S_un.S_un_b.s_b3,
		saClientAddress.sin_addr.S_un.S_un_b.s_b4);

	return SAGA_OK;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Connect
//	Purpose:
//		Connect the socket attribute to a given server address/name at
//		the given port number. 
HRESULT SAGASocketObject::Connect(char *chServer, int nPort) 
{
	sockaddr_in saServerAddress;
	LPHOSTENT   pHost=NULL;

	// try to find the server by address or name
	memset(&saServerAddress,0,sizeof(sockaddr_in));
	saServerAddress.sin_port        = htons(nPort);
	saServerAddress.sin_family      = AF_INET;
	saServerAddress.sin_addr.s_addr = inet_addr(chServer);

	if (saServerAddress.sin_addr.s_addr==INADDR_NONE) 
	{
		pHost = gethostbyname(chServer);
		if (pHost != NULL) saServerAddress.sin_addr.s_addr = ((LPIN_ADDR)pHost->h_addr)->s_addr;
		else 
		{
			Log("error: server \"%s\" not found", chServer);
			return SAGA_FAIL;
		}
	}

	// connect the socket to the server address
	if (connect(m_skSocket, (sockaddr*)&saServerAddress, sizeof(sockaddr)) == SOCKET_ERROR) 
	{
		LogLastWSAError("connect() in SAGASocketObject::Connect failed");
		Disconnect();
		return SAGA_FAIL;
	}

	// now the SocketObject is active
	m_bRunning = true;
	return SAGA_OK;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Disconnect
//	Purpose:
//		Just skip the connection of the active socket.
void SAGASocketObject::Disconnect() 
{
	if (m_skSocket != INVALID_SOCKET) 
	{
		shutdown(m_skSocket, 0x02);
		closesocket(m_skSocket);
		m_skSocket = INVALID_SOCKET;
		Log("socket closed");
	}
}
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Send
//	Purpose:
//		Send data encapsulation for WinSock.
int SAGASocketObject::Send(const char *pPkg, UINT nSize) 
{
	UINT nSent=0;
	UINT n=0;

	while (nSent < nSize) 
	{
		n = send(m_skSocket, pPkg+nSent, nSize-nSent, 0);
		if (n==SOCKET_ERROR) 
		{
			LogLastWSAError("send() in SAGASocketObject");
			return n;
		}
		else nSent += n;
	}
	return nSent;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Send
int SAGASocketObject::Send(const char *pPkg, UINT nSize, SOCKET skReceiver) 
{
	UINT nSent=0;
	UINT n=0;

	while (nSent < nSize) 
	{
		n = send(skReceiver, pPkg+nSent, nSize-nSent, 0);
		if (n==SOCKET_ERROR) 
		{
			LogLastWSAError("send() in SAGASocketObject");
			return n;
		}
		else nSent += n;
	}
	return nSent;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Receive
//	Purpose:
//		Receive waiting data from socket in reaction to FD_READ message.
HRESULT SAGASocketObject::Receive(SOCKET sk) 
{
	HRESULT hr         = SAGA_OK;
	UINT    nSize      = 65536;   // max size to read in one call
	UINT    nBytesRead = 0;       // actual bytes read in one call
	UINT    nReadHead  = 0;       // reading position in m_Buffer
	UINT    n          = 0;       // data in buffer remaining
	bool    bDone      = false;   // ready

	SAGAPACKAGE *pPkg          = NULL;
	UINT        nPkgSizeTotal = 0;

	// read up to 65536 bytes each call and loop until
	// no more data is waiting at socket to be received
	while (!bDone) 
	{

		nBytesRead = recv(sk, &m_Buffer[n], nSize-n, 0);

		if (nBytesRead == SOCKET_ERROR) 
		{
			int WSAError = WSAGetLastError();

			// ignore non-critical errors
			if ( (WSAError != WSAEMSGSIZE) && (WSAError != WSAEWOULDBLOCK) ) 
			{
				LogLastWSAError("recv() in SAGASocketObject::Receive");
				hr = SAGA_FAIL;
				bDone = true;
				break;
			}
		}

		// new got nBytesRead bytes in m_Buffer so enqueue it
		if (nBytesRead <= 0) bDone = true;
		else 
		{
			// take care of old data in the buffer
			nBytesRead += n;

			// loop as long as we find another complete header
			// in our buffer, note that a package could have
			// been split so take care of that
			while ( (nBytesRead - nReadHead) > g_PkgSize ) 
			{
				// mask next chunk of data as SAGAPACKAGE
				pPkg = (SAGAPACKAGE*)&m_Buffer[nReadHead];
				pPkg->pData = &m_Buffer[nReadHead] + g_PkgSize;

				// how big is this package?
				nPkgSizeTotal = g_PkgSize + pPkg->nLength;

				// do we have the whole package received
				if ( (nBytesRead-nReadHead) >= (nPkgSizeTotal) ) 
				{
					m_pInbox->Enqueue(pPkg, nPkgSizeTotal);
					nReadHead += nPkgSizeTotal;
				}
				// no just part of the package so go back to recv
				else 
				{
					// copy the split package to start of the buffer
					memcpy(m_Buffer, &m_Buffer[nReadHead], nBytesRead-nReadHead);
					n = nBytesRead-nReadHead;
					break;
				}
			} // while

			// if we already got all data that was waiting
			if (nBytesRead < nSize) bDone = true;
		}
	} // while
	return hr;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: GetNextPkg
//	Purpose:
//		Get the next element waiting in our inbox for processing.
HRESULT SAGASocketObject::GetNextPkg(SAGAPACKAGE *pPkg) 
{
	// is there a package at all?
	if (m_pInbox->GetCount() > 0) 
	{
		// stream serialized data into our buffer
		m_pInbox->Front(m_Buffer, true);

		// fill it into the structure
		memcpy(pPkg, m_Buffer, g_PkgSize-sizeof(PVOID));
		memcpy(pPkg->pData, m_Buffer + g_PkgSize, pPkg->nLength);

		return SAGA_OK;
	}
	return SAGA_FAIL;
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: FeedByHand
//	Purpose:
//		Feed a package by hand into the sockets inbox
void SAGASocketObject::FeedByHand(SAGAPACKAGE *pPkg) 
{
	int nPkgSizeTotal = g_PkgSize + pPkg->nLength;
	m_pInbox->Enqueue(pPkg, nPkgSizeTotal);
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: Log
//	Purpose:
//		write outputstring to attribut outputstream if exists
//		Parameters:
//			bool - flush immediately
//			char - format string to output
//			...  - output values
void SAGASocketObject::Log(char *chString, ...) 
{
	char ch[256];
	char *pArgs;

	pArgs = (char*) &chString + sizeof(chString);
	vsprintf(ch, chString, pArgs);
	fprintf(m_pLog, "[SAGASocktObj]: ");
	fprintf(m_pLog, ch);
	fprintf(m_pLog, "\n");

	if (g_bLF) fflush(m_pLog);
} 
//
////////////////////////////////////////

////////////////////////////////////////
//	Function Name: LogLastWSAError
//	Purpose:
//		Get the last WSA error an put it out to the log file together
//		with the given string an prefix "error: ".
void SAGASocketObject::LogLastWSAError(const char *error) 
{
	int n=WSAGetLastError();

	if      (n==WSANOTINITIALISED)	Log("error: %s | WSANOTINITIALISED", error);
	else if (n==WSAENETDOWN)		Log("error: %s | WSAENETDOWN", error);
	else if (n==WSAEADDRINUSE)		Log("error: %s | WSAEADDRINUSE", error);
	else if (n==WSAEINTR)			Log("error: %s | WSAEINTR", error);
	else if (n==WSAEINPROGRESS)		Log("error: %s | WSAEINPROGRESS", error);
	else if (n==WSAEALREADY)		Log("error: %s | WSAEALREADY", error);
	else if (n==WSAEADDRNOTAVAIL)	Log("error: %s | WSAEADDRNOTAVAIL", error);
	else if (n==WSAEAFNOSUPPORT)	Log("error: %s | WSAEAFNOSUPPORT", error);
	else if (n==WSAECONNREFUSED)	Log("error: %s | WSAECONNREFUSED", error);
	else if (n==WSAEFAULT)			Log("error: %s | WSAEFAULT", error);
	else if (n==WSAEINVAL)			Log("error: %s | WSAEINVAL", error);
	else if (n==WSAEISCONN)			Log("error: %s | WSAEISCONN", error);
	else if (n==WSAENETUNREACH)		Log("error: %s | WSAENETUNREACH", error);
	else if (n==WSAENOBUFS)			Log("error: %s | WSAENOBUFS", error);
	else if (n==WSAENOTSOCK)		Log("error: %s | WSAENOTSOCK", error);
	else if (n==WSAENOTCONN)		Log("error: %s | WSAENOTCONN", error);
	else if (n==WSAETIMEDOUT)		Log("error: %s | WSAETIMEDOUT", error);
	else if (n==WSAEWOULDBLOCK)		Log("error: %s | WSAEWOULDBLOCK", error);
	else if (n==WSAEACCES)			Log("error: %s | WSAEACCES", error);
	else if (n==WSAEMFILE)			Log("error: %s | WSAEMFILE", error);
	else if (n==WSAEOPNOTSUPP)		Log("error: %s | WSAEOPNOTSUPP", error);
	else							Log("error: %s | unknown error code", error);
} 
//
////////////////////////////////////////